#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank-n.h>
#include <mach/map.h>
#include <mach/gpio.h>
#include <linux/poll.h>

#include <linux/sched.h>

char *name = "fifth_dev";
static struct class *fifth_dev_class;

static struct fasync_struct *my_async;

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

static volatile int ev_press = 0;

volatile unsigned long *gpncon;
volatile unsigned long *gpndat;

struct pin_desc {
	unsigned int pin;
	unsigned int key_val;
};

static unsigned char key_val;

struct pin_desc pins_desc[4] = {
	{S3C64XX_GPN(0), 0x01}, 
	{S3C64XX_GPN(1), 0x02}, 
	{S3C64XX_GPN(2), 0x03}, 
	{S3C64XX_GPN(3), 0x04},
};

static irqreturn_t buttons_irq(int irq, void *dev_id)
{
	struct pin_desc *pindesc = (struct pin_desc *)dev_id;

	unsigned int pinval;

	pinval = readl(S3C64XX_GPNDAT);
	
	if (pinval) {
		key_val = 0x80 | pindesc->key_val;
	} else {
		key_val = pindesc->key_val;
	}
	
	ev_press = 1;
	wake_up_interruptible(&button_waitq);

	kill_fasync(&my_async, SIGIO, POLL_IN);

	return IRQ_RETVAL(IRQ_HANDLED);	
}

static int fifth_dev_open(struct inode *inode, struct file *file)
{
	if (request_irq(IRQ_EINT(0), buttons_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "S2", &pins_desc[0])) {
		return -EINVAL;	
	}
	if (request_irq(IRQ_EINT(1), buttons_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "S3", &pins_desc[1])) {
		return -EINVAL;
	}
	if (request_irq(IRQ_EINT(2), buttons_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "S4", &pins_desc[2])) {
		return -EINVAL;
	}
	if (request_irq(IRQ_EINT(3), buttons_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "S5", &pins_desc[3])) {
		return -EINVAL;
	}
	
	return 0;	
}
/*
static unsigned fifth_dev_poll(struct file *file, poll_table *wait)
{
	unsigned int mask = 0;
	poll_wait(file, &button_waitq, wait);

	if (ev_press) {
		mask |= POLLIN | POLLRDNORM;
	}

	return mask;
}
*/

static int fifth_dev_fasync(int fd, struct file *filp, int on)
{
	printk("driver: fifth_drv_fasync\r\n");
	return fasync_helper(fd, filp, on, &my_async);
}

static ssize_t fifth_dev_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
	if (size != 1) {
		return -EINVAL;
	}	

	wait_event_interruptible(button_waitq, ev_press);

	if (copy_to_user(buf, &key_val, 1)) {
		return -EINVAL;
	}

	ev_press = 0;

	return 1;
}

static int fifth_dev_close(struct inode *inode, struct file *file)
{
	free_irq(IRQ_EINT(0), &pins_desc[0]);
	free_irq(IRQ_EINT(1), &pins_desc[1]);
	free_irq(IRQ_EINT(2), &pins_desc[2]);
	free_irq(IRQ_EINT(3), &pins_desc[3]);
	return 0;
}

struct file_operations fifth_dev_fops = {
	.owner = THIS_MODULE,
	.open = fifth_dev_open,
	.read = fifth_dev_read,
	.release = fifth_dev_close,
//	.poll = fifth_dev_poll,
	.fasync = fifth_dev_fasync,
};

int major;

static int __init fifth_dev_init(void) 
{
	major = register_chrdev(0, name, &fifth_dev_fops);

	fifth_dev_class = class_create(THIS_MODULE, name);
	device_create(fifth_dev_class, NULL, MKDEV(major, 0), &fifth_dev_init, "%s", name);	
	
	printk("%s_init and the device is %d\n", name, major);

	gpncon = (volatile unsigned long *)ioremap(0x7F008830, 16);
	gpndat = gpncon + 1;

	return 0;
}

static void __exit fifth_dev_exit(void)
{
	unregister_chrdev(major, name);
	
	device_destroy(fifth_dev_class, MKDEV(major, 0));
	class_destroy(fifth_dev_class);
	
	printk("%s_exit and device is %d\n", name, major);
	
	iounmap(gpncon);
}

MODULE_LICENSE("GPL");
module_init(fifth_dev_init);
module_exit(fifth_dev_exit);
